//=============================================================================
// Sentinel - Base class for automatic sentry turrets.
// I think I need to buy a book on vector maths for doing this sort of thing.
//=============================================================================
class Sentinel extends BaseSentinel
	abstract;

#EXEC OBJ LOAD FILE=XGameShaders.utx

var float CurrentYawSpeed, CurrentPitchSpeed;
var() float YawSpeed, PitchSpeed;
var() float MaxPitch, MinPitch;
var float DesiredYaw, DesiredPitch;
var rotator CurrentAim;

var() sound AquiredSound;
var() sound ActivateSound;

var() float AimAhead;
var() float MaxAimError;
var() float HearingAdjustment;
var() float SensorAngle;
var() int AmmoCost;

var Pawn Target;

var Decoration TeamMarker;
var vector TeamMarkerOffset;

var SentinelUpgrade Upgrade; //More power!
var SentinelWeapon SWeapon; //The bit that does the actual firing.
var class<SentinelWeapon> DefaultWeaponClass; //Starting weapon.
var string DefaultWeap;


replication
{
	reliable if(Role == ROLE_Authority)
		CheckUpgrades,Target, Upgrade, SWeapon;

	unreliable if(Role == ROLE_Authority)
		CurrentAim, DesiredYaw, DesiredPitch;
}

event PreBeginPlay(){}
function DropToGround(){}
function AddVelocity(vector NewVelocity){}

simulated function PostBeginPlay()
{
	SetRotation(rot(0, 0, 0));

	if(Role < ROLE_Authority)
		return;

	SpawnTime = Level.TimeSeconds;

	if(ControllerClass != None && Controller == None)
		Controller = spawn(ControllerClass);

	Skins[0] = FinalBlend'XGameShaders.PlayerShaders.InvisFinal';
	RepSkin = Skins[0];

	if(string(Level.GetGameClass()) ~= "SkaarjPack.Invasion")
		bIsInvasion = true;
}

//Set up instigator and team stuff.
function InitializeFor(Pawn Placer)
{
	if(Placer == None || Placer.Controller == None || Placer.Controller.PlayerReplicationInfo == None || Controller == None)
	{
		Destroy();
		return;
	}

	Instigator = Placer;
	InstigatorController = Placer.Controller;
	myteam=placer.PlayerReplicationInfo.Team.TeamIndex;
	Controller.Possess(self);

	if(SWeapon == None && DefaultWeaponClass != None)
      CreateUpgrade(DefaultWeap, 0,Instigator);
}

//So the Sentinel's team is easily visible.
simulated function SetTeamMarker()
{
	if(TeamMarker != None || PlayerReplicationInfo == None || PlayerReplicationInfo.Team == None)
		return;

	if(bIsInvasion)
		return;

	if(SWeapon != None)
		SWeapon.TeamIndex = PlayerReplicationInfo.Team.TeamIndex;

	if(Level.NetMode == NM_DedicatedServer)
		return;

	if(TeamMarker == None)
	{
		TeamMarker = Spawn(class'PlayerLight',,, Location + TeamMarkerOffset);
		TeamMarker.SetBase(self);
	}

	if(PlayerReplicationInfo.Team.TeamIndex == 0)
		TeamMarker.Texture = Texture'RedMarker_t';
	else
		TeamMarker.Texture = Texture'BlueMarker_t';
}

simulated function rotator GetViewRotation()
{
	return CurrentAim;
}

//Kill Sentinels when round resets in BR, otherwise they get forcibly moved to a playerstart.
function PlayTeleportEffect(bool bOut, bool bSound)
{
	bForceDeath = true;
	SetTimer(0.1, false);
}

//Fix for ubiquitous no-instigator bug.
function RenewInstigator()
{
	if(InstigatorController == None)
		Died(None, None, Location);

	if((Instigator == None || Instigator == self) && InstigatorController.Pawn != None)
		Instigator = InstigatorController.Pawn;
}

simulated function bool CheckUpgrades(string aclassname)
{
    local class<SentinelUpgrade> Checkupgrade;

	Checkupgrade = class<SentinelUpgrade>(DynamicLoadObject(aClassName, class'Class'));

	if(HasUpgrade(Checkupgrade)==true)
	   return true;

return false;
}

function CheckWeapon()
{
	local int i;
	local bool repeat;
	local float lcost;
    local string leftS, rightS;
    local int Checklimit;

    repeat=false;
    pri=tfpri(self.PlayerReplicationInfo);
    i=0;
    Checklimit = 6;

	while(i<Checklimit && repeat==false)
	{
     	if(CheckUpgrades(class 'TFMenuConsole'.default.SentinelTurret[i])==true)
		{
     			divide(class 'TFMenuConsole'.default.SentinelTOption[i]," ",lefts,rightS);
     			if(lefts=="") lcost=0;
     			else lcost=float(lefts);

     			tfgri(level.game.gamereplicationinfo).control[myteam]-=lcost;
     			repeat=true;
		}
			i++;
	}
}

event TakeDamage(int Damage, Pawn EventInstigator, vector HitLocation, vector Momentum, class<DamageType> DamageType)
{
  local playercontroller pc;
  local controller p;
  local int num, i;

    spri=SentinelPlayerReplicationInfo(PlayerReplicationInfo);

	if(Role < ROLE_Authority || bTearOff || Health <= 0)
		return;

	//Ion Cannon are wimpy, so give them a little help.
	if(DamageType == class'DamTypeIonBlast')
		Damage *= 2;

	if(EventInstigator != None && Controller != None && Controller.SameTeamAs(EventInstigator.Controller) && TeamGame(Level.Game) != None)
		Damage *= TeamGame(Level.Game).FriendlyFireScale;

	//Let upgrades handle damage.
	if(Upgrade != None)
		Upgrade.NotifyTakeDamage(Damage, EventInstigator, HitLocation, Momentum, DamageType);

	Health -= Damage;

    if(damage>0)
    {
	    num=fmin(damage/3,20);
        if(level.bdropdetail) num/=2;

        for(i=0; i<num; i++)
	    {
            //if ( EffectIsRelevant(hitlocation,false) )
            //{
                spawn(class'TaskForces.tfStationSparks',,,hitlocation,rotator(Normal(hitlocation - location)));
            //}
        }

        for ( P = Level.ControllerList; P!=None; P=P.nextController )
        {
            pc=playercontroller(p);

            if(pc!=none && p.PlayerReplicationInfo.Team.TeamIndex==myteam)
               pc.ReceiveLocalizedMessage(class'TaskForces.tfSentinelDmgMsg',damage,,,self);
        }
    }

	if(Health <= 0)
	{
		if(EventInstigator != None)
			Died(EventInstigator.Controller, DamageType, HitLocation);
		else
			Died(None, DamageType, HitLocation);
	}

	else
	{
		PlayHit(Damage, EventInstigator, HitLocation, DamageType, Momentum);

		if(Controller != None)
			Controller.NotifyTakeHit(EventInstigator, HitLocation, Damage, DamageType, Momentum);
    }
	if(EventInstigator!=none)
        {
            if(EventInstigator.playerreplicationinfo.team.teamindex==myteam)
            {
                EventInstigator.playerreplicationinfo.score-=float(damage)/200;
                tfpri(EventInstigator.playerreplicationinfo).SubSkill(-float(damage)/10);
            }
            else
            {
                EventInstigator.playerreplicationinfo.score+=float(damage)/200;
                tfpri(EventInstigator.playerreplicationinfo).AddSkill(float(damage)/10);
            }
        }
}

function setrecyclestate()
{
    brecycled=true;
}

function Died(Controller Killer, class<DamageType> DamageType, vector HitLocation)
{
    local controller c;

    pri=tfpri(Killer.PlayerReplicationInfo);

    if(killer!=none && killer.PlayerReplicationInfo!=none)
    {
        if(killer.PlayerReplicationInfo.Team.TeamIndex!=myteam)
        {
            //SCORE************************
            killer.PlayerReplicationInfo.Score+=cost/100;
            tfpri(killer.PlayerReplicationInfo).AddSkill(cost/10);
            tfpri(killer.PlayerReplicationInfo).AddRes(cost/10);
            RenewInstigator();
            HurtRadius(Default.Health * 0.125, (CollisionRadius + CollisionHeight) * 3, class'DamTypeSentinelExplosion', 10000, Location);
        }
        else //destroyed own building
        {
            //SCORE************************
            if(!brecycled)
            {
                killer.PlayerReplicationInfo.Score-=cost/100;
                tfpri(killer.PlayerReplicationInfo).SubSkill(-cost/50);
                tfpri(killer.PlayerReplicationInfo).SubRes(-cost/10);
            }
            else
            {
                killer.PlayerReplicationInfo.Score-=cost/200;
                tfpri(killer.PlayerReplicationInfo).SubSkill(-cost/100);
                tfpri(killer.PlayerReplicationInfo).AddRes(cost/2);
            }
        }
    }

    for ( c = Level.ControllerList; c!=None; c=c.nextController )
    {
        if(playercontroller(c)!=none && c.PlayerReplicationInfo.team.TeamIndex==myteam)
            playercontroller(c).ReceiveLocalizedMessage(class'TaskForces.TFSLoseMsg',,killer.playerreplicationinfo,,self);
    }
    ////////////////////
	if(Upgrade != None)
		Upgrade.Died();

	if(Controller != None)
	{
		Controller.PawnDied(self);
		PlayDying(DamageType, HitLocation);
	}
	else
		Destroy();
}

//Damage telefraggers.
event EncroachedBy(Actor Other)
{
	if(Pawn(Other) != None && Vehicle(Other) == None)
	{
		gibbedBy(Other);
		Other.TakeDamage(Default.Health * 0.125, Instigator, Location, normal(Other.Location - Location) * 10000, class'DamTypeSentinelExplosion');
	}
}

simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
	if(bDied)
		return;

	bTearOff = true;
	LifeSpan = 0.1;

	if(Level.NetMode != NM_DedicatedServer)
	{
		PlaySound(sound'WeaponSounds.BExplosion3',, 2.5*TransientSoundVolume);
		Spawn(class'RocketExplosion',,, Location, rotator(vect(0, 0, -1)));
		Spawn(class'ExplosionCrap',,, Location, rotator(vect(0, 0, -1)));
		Spawn(class'RocketMark', self,, Location + vect(0, 0, -46), rotator(vect(0, 0, -1)));
		SpawnGibs();
	}

	bDied = true;
}

function JumpOffPawn()
{
	Velocity += (100 + CollisionRadius) * VRand();
	Velocity.Z = 200 + CollisionHeight;
	SetPhysics(PHYS_Falling);
	bNoJumpAdjust = true;
}

event BaseChange()
{
	if(Controller == None || bWasBased)
		Destroy();

	Super.BaseChange();

	if(Base != None)
	{
		SetCollision(true, true, true);
		bCollideWorld = false; //Prevents vehicles from pushing Sentinels and stops Sentinels based on vehicles from sliding off.
		SetPhysics(PHYS_None);
		Skins[0] = Default.Skins[0];
		RepSkin = Skins[0];

		bWasBased = true;

		if(bActorShadows && class'xPawn'.Default.bPlayerShadows && Level.NetMode != NM_DedicatedServer)
		{
			PlayerShadow = Spawn(class'ShadowProjector',Self,'',Location);
			PlayerShadow.ShadowActor = self;
			PlayerShadow.bBlobShadow = class'xPawn'.Default.bBlobShadow;
			PlayerShadow.LightDirection = Normal(ShadowDir);
			PlayerShadow.LightDistance = 320;
			PlayerShadow.MaxTraceDistance = 350;
			PlayerShadow.InitShadow();
		}

		//Fix for Sentinel getting based before it is initialized.
		if(SentinelController(Controller).sent == None)
			SentinelController(Controller).sent = self;

		Controller.GotoState('Waiting');
	}
	else
		bCollideWorld = true;
}

event PhysicsVolumeChange(PhysicsVolume NewVolume)
{
	if(NewVolume.bWaterVolume)
		Died(None, None, Location);
}

//So Sentinels can't pick up the flag (D'oh!).
simulated function bool IsPlayerPawn()
{
	return false;
}

//Can't place two Sentinels on top of each other.
event bool EncroachingOn(actor Other)
{
	if(Other.IsA('Sentinel'))
		Destroy();

	return false;
}

//Will be destroyed if placed under a mover.
function PlayMoverHitSound()
{
	TakeDamage(250, None, Location, vect(0, 0, 0), class'Crushed');
}

simulated singular function Touch(Actor Other)
{
	if(Other == None)
		return;

	//Force grenades and bioglobs to explode when they hit a Sentinel.
	if(Other.IsA('Grenade'))
		Projectile(Other).Explode(Other.Location, Normal(Other.Velocity));
	else if(Other.IsA('BioGlob'))
		Projectile(Other).BlowUp(Other.Location);
	else if(Other.IsA('TransBeacon') && VSize(Other.Velocity) < 500) //Make telefragging more difficult.
		Other.Velocity *= 3.0;
}

simulated function Tick(float dt)
{
	if(bDied)
		return;

	if(bTearOff)
	{
		PlayDying(None, Location);
		return;
	}

	//Make very sure that team marker has been spawned.
	SetTeamMarker();

	CurrentAim = Normalize(CurrentAim);

	//Work out how best to rotate towards the target.
	//Slowly pan around until a target is aquired.
	//Don't acquire players as targets if that player has a Jammer.
	if(Target == none || target.FindInventoryType(class'TFJammer')!=none)
	{
		DesiredPitch = 0;
		DesiredYaw = CurrentAim.Roll + 300;
	}

	//Add a bit of damping, to reduce jitter.
	if(Abs(DesiredPitch - CurrentAim.Yaw) < 2048)
		CurrentPitchSpeed = PitchSpeed * Abs(DesiredPitch - CurrentAim.Yaw) / 2048;
	else
		CurrentPitchSpeed = PitchSpeed;

	if(Abs(DesiredYaw  - CurrentAim.Roll) < 2048)
		CurrentYawSpeed = YawSpeed * Abs(DesiredYaw  - CurrentAim.Roll) / 2048;
	else
		CurrentYawSpeed = YawSpeed;

	//Rotate towards the target as fast as possible.
	if(DesiredYaw ClockWiseFrom CurrentAim.Roll)
	{
		DesiredYaw = CurrentAim.Roll + dt * CurrentYawSpeed;

		if(!(DesiredYaw ClockWiseFrom CurrentAim.Roll))
			DesiredYaw = CurrentAim.Roll;
	}
	else
	{
		DesiredYaw = CurrentAim.Roll - dt * CurrentYawSpeed;

		if(DesiredYaw ClockWiseFrom CurrentAim.Roll)
			DesiredYaw = CurrentAim.Roll;
	}

	if(DesiredPitch ClockWiseFrom CurrentAim.Yaw)
	{
		DesiredPitch = CurrentAim.Yaw + dt * CurrentPitchSpeed;

		if(!(DesiredPitch ClockWiseFrom CurrentAim.Yaw))
			DesiredPitch = CurrentAim.Yaw;
	}
	else
	{
		DesiredPitch = CurrentAim.Yaw  - dt * CurrentPitchSpeed;

		if(DesiredPitch ClockWiseFrom CurrentAim.Yaw)
			DesiredPitch = CurrentAim.Yaw;
	}

	DesiredYaw = DesiredYaw % 65536;
	DesiredPitch = DesiredPitch % 65536;

	//Limit rotation.
	if(DesiredPitch > MaxPitch)
	{
		DesiredPitch = MaxPitch;
		PitchSpeed = 0;
	}
	else if(DesiredPitch < MinPitch)
	{
		DesiredPitch = MinPitch;
		PitchSpeed = 0;
	}
	else
		PitchSpeed = Default.PitchSpeed;

	CurrentAim.Roll = int(DesiredYaw);
	CurrentAim.Yaw = int(DesiredPitch);
	CurrentAim.Pitch = 0;

	RotateCannon();
}

simulated function RotateCannon()
{
	local Rotator r;

	//Ugly stuff this: rotate Sentinel to face aiming direction, then rotate base backwards the same amount to make it appear to stay still.
	r.Yaw = CurrentAim.Roll;
	SetRotation(r);
	r.Yaw = -CurrentAim.Roll;
	SetBoneRotation('bone_base', r, 0, 1);

	SetBoneRotation('Bone_pivot01', CurrentAim, 0, 1);
}

simulated event Destroyed()
{
	local int i;

	if(TeamMarker != None)
		TeamMarker.Destroy();

	if(PlayerShadow != None)
		PlayerShadow.Destroy();

	for(i = 0; i < Attached.length; i++)
	{
		if(Attached[i].IsA('xEmitter') && !Attached[i].IsA('BloodJet'))
			xEmitter(Attached[i]).mRegen = false;
	}

	if(Upgrade != None)
		Upgrade.RemoveUpgrade(true);

	Super.Destroyed();
}

function PlayHit(float Damage, Pawn InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum)
{
	local Actor a;
	local class<Actor> EmitterClass;
	local int i, EffectCount;

	if(Damage < 1)
		return;

	if(Upgrade != None)
		Upgrade.PlayHit();

	SetOverlayMaterial(DamageOverlay, DamageOverlayTime, false);

	PlaySound(DamageSounds[Rand(4)],, 1.5*TransientSoundVolume);

	EmitterClass = GibGroupClass.static.GetBloodEmitClass();

	for(i = 0; i < Attached.Length; i++)
	{
		if(Attached[i] == None)
			continue;

		if(ClassIsChildOf(EmitterClass, Attached[i].Class))
			EffectCount++;
	}

	if(EffectCount > 3)
		return;

	a = Spawn(EmitterClass,,, HitLocation, rotator(-Momentum));
	a.SetBase(self);
}

simulated function SpawnGibs()
{
        SpawnGiblet(GibGroupClass.static.GetGibClass(EGT_Calf));
        SpawnGiblet(GibGroupClass.static.GetGibClass(EGT_Forearm));
        SpawnGiblet(GibGroupClass.static.GetGibClass(EGT_Hand));
        SpawnGiblet(GibGroupClass.static.GetGibClass(EGT_Head));
	SpawnGiblet(GibGroupClass.static.GetGibClass(EGT_Torso));
        SpawnGiblet(GibGroupClass.static.GetGibClass(EGT_Upperarm));
}

simulated function SpawnGiblet(class<Gib> GibClass)
{
	local Gib Giblet;
	local rotator GibRotation;
	local vector GibLocation;

	Giblet = Spawn(GibClass);

	if(Giblet == None)
		return;

	GibLocation = Location;
	GibLocation.Y -= 56;
	GibLocation.Y += FRand() * 112;
	Giblet.SetLocation(GibLocation);

	GibRotation.Pitch = FRand() * 65536;
	GibRotation.Yaw = FRand() * 65536;
	GibRotation.Roll = FRand() * 65536;

	Giblet.Velocity = vector(GibRotation) * FRand() * 512.0;
}

function NewTarget(Pawn NewEnemy)
{
	Target = NewEnemy;

	//Tell inventory of new target.
	if(Upgrade != None)
		Upgrade.NewTarget(NewEnemy);
}

function SetWaiting()
{
	Target = None;

	//Tell inventory that sentinel is idle.
	if(Upgrade != None)
		Upgrade.SetWaiting();
}

function bool FireAt(vector Spot)
{
	local rotator Aim;
	local vector Origin;
	local vector HeadSpot;

	Origin = Location;
	Origin.Z += BaseEyeHeight;

	//Target too far away.
	if(VSize(Target.Location - Origin) > FMin(SightRadius, SWeapon.MaxRange()))
		return false;

	//Target not in field of fire.
	if(Abs(DesiredPitch - CurrentAim.Yaw) > MaxAimError || Abs(DesiredYaw  - CurrentAim.Roll) > MaxAimError)
		return false;

	Aim.Yaw = CurrentAim.Roll;
	Aim.Pitch = CurrentAim.Yaw;

	if(!Controller.LineOfSightTo(Target))
		return false;

	//Possibly try for a headshot, if the middle is not in line-of-sight and the cannon is going for a direct shot.
	//TODO: Maybe allow for non-zero sized projectiles.
	//TODO: Maybe try other spots too (sides, feet).
	if(VSize(Spot - Target.Location) < 100 && !FastTrace(Spot, Origin))
	{
		HeadSpot = Spot;
		HeadSpot.Z += 0.9 * Target.CollisionHeight;

		if(FastTrace(HeadSpot, Origin))
			Spot = HeadSpot;
		else
			return false;
	}

	RenewInstigator();

	if(SWeapon != None)
		SWeapon.FireAt(Origin, vector(Aim), normal(Spot - Origin));

	if(Upgrade != None)
		Upgrade.Fired();

	return true;
}

function int AmmoValue()
{
	return (0.5 * AmmoCost * Health / Default.Health);
}

//=============================================================================
// Upgrade functions:
//=============================================================================

function bool CreateUpgrade(string aclassname, float cost, pawn user)
{
	local class<SentinelUpgrade> NewUpgradeClass;
    local SentinelUpgrade NewUpgrade;

	NewUpgradeClass = class<SentinelUpgrade>(DynamicLoadObject(aClassName, class'class'));

	if(!AllowUpgrade(NewUpgradeClass,user,cost))
	{
	   user.PlaySound(sound'TFMedia.tfGUIFail',SLOT_None,TransientSoundVolume);
	   return false;
	}

	NewUpgrade = spawn(NewUpgradeClass, self);

        if(NewUpgrade != None)
        {
          if(NewUpgrade.IsA('SentinelWeapon'))
          {
			if(SWeapon != None)
			{
                CheckWeapon();
				SWeapon.RemoveUpgrade();
            }
          }
         if(Upgrade != None)
			Upgrade.AddUpgrade(NewUpgrade);
		 else
			Upgrade = NewUpgrade;

		NewUpgrade.SetBase(self);
		NewUpgrade.InitFor(self);

          if(NewUpgrade.IsA('SentinelWeapon'))
          {
			SWeapon = SentinelWeapon(NewUpgrade);

			Upgrade.ChangedWeapon();
			Controller.ChangedWeapon();
          }
        if(pri != none)
    		pri.SubRES(-cost);
		tfgri(level.game.gamereplicationinfo).control[myteam]+=cost;
		Playercontroller(user.Controller).ClientPlaySound(Sound'MenuSounds.selectJ');

		//log("Created upgrade:"@NewUpgrade);

		return true;
        }
	return false;
}

//Prevent upgrades here.
simulated function bool AllowUpgrade(class<SentinelUpgrade> NewUpgradeClass,pawn user,optional float cost)
{
pri=tfpri(user.PlayerReplicationInfo);

	if(NewUpgradeClass == None)
		return false;

	//Do not allow two of the same upgrade.
	if(HasUpgrade(NewUpgradeClass))
	{
	user.ReceiveLocalizedMessage(class'DeployerMessage', 6);
	return false;
	}

    if(TFGame(level.game).AllowUpgrades == False)
    {
    user.ReceiveLocalizedMessage(class'DeployerMessage', 7);
    return false;
    }

    if(cost>pri.RES)
    {
    user.ReceiveLocalizedMessage(class'DeployerMessage', 2);
    return false;
    }
    if(CheckControl(cost,myteam)==false)
	{
	 Instigator.ReceiveLocalizedMessage(class'DeployerMessage', 0);
	 return false;
    }

	return true;
}

function bool CheckControl(int costs, int team)
{
    if(tfgri(level.game.gamereplicationinfo).control[team]+costs>tfgri(level.game.gamereplicationinfo).maxcontrol[team])
    return false;

    return true;
}

//Returns true if this upgrade has already been applied.
simulated function bool HasUpgrade(class<SentinelUpgrade> NewUpgradeClass)
{
	local SentinelUpgrade U;

	if(Upgrade == None)
		return false;

	for(U = Upgrade; U != None; U = U.NextUpgrade)
	{
		if(U.Class == NewUpgradeClass)
		return true;

		//Just in case the list turns into a loop.
		if(U.NextUpgrade != None && U.NextUpgrade == Upgrade)
			U.NextUpgrade = None;
	}

	return false;
}

//=============================================================================
// Debugging:
//=============================================================================

simulated function DisplayDebug(Canvas Canvas, out float YL, out float YPos)
{
	local SentinelUpgrade U;

	Super.DisplayDebug(Canvas, YL, YPos);

	Canvas.SetDrawColor(255,255,255);

	Canvas.DrawText("CurrentYawSpeed:"@CurrentYawSpeed@"CurrentPitchSpeed:"@CurrentPitchSpeed);
	YPos += YL;
	Canvas.SetPos(4,YPos);

	Canvas.DrawText("DesiredYaw:"@DesiredYaw@"DesiredPitch:"@DesiredPitch);
	YPos += YL;
	Canvas.SetPos(4,YPos);

	Canvas.DrawText("CurrentYaw:"@normalize(CurrentAim).Roll@"CurrentPitch:"@normalize(CurrentAim).Yaw);
	YPos += YL;
	Canvas.SetPos(4,YPos);

	if(Upgrade == None)
		return;

	for(U = Upgrade; U != None; U = U.NextUpgrade)
	{
		Canvas.DrawText("Upgrade:"@U);
		YPos += YL;
		Canvas.SetPos(4,YPos);
	}
}

defaultproperties
{
     YawSpeed=30000.000000
     PitchSpeed=30000.000000
     maxPitch=8192.000000
     MinPitch=-8192.000000
     AimAhead=1.000000
     MaxAimError=4096.000000
     SensorAngle=90.000000
     AmmoCost=1
     GibGroupClass=Class'XEffects.xBotGibGroup'
     DamageOverlay=Shader'XGameShaders.PlayerShaders.LightningHit'
     DamageOverlayTime=1.000000
     DamageSounds(0)=Sound'WeaponSounds.BaseImpactAndExplosions.BBulletImpact11'
     DamageSounds(1)=Sound'WeaponSounds.BaseImpactAndExplosions.BBulletImpact12'
     DamageSounds(2)=Sound'WeaponSounds.BaseImpactAndExplosions.BBulletImpact13'
     DamageSounds(3)=sound'WeaponSounds.BaseImpactAndExplosions.BShieldReflection'
     ShadowDir=(X=1.000000,Y=1.000000,Z=3.000000)
     AquiredSound=Sound'GeneralAmbience.beep7'
     ActivateSound=Sound'GeneralAmbience.beep2'
     HealthMax=1000.000000
     bCanJump=False
     bCanWalk=False
     bCanDoubleJump=False
     SightRadius=10000.000000
     MaxFallSpeed=400.000000
     BaseEyeHeight=0.000000
     EyeHeight=0.000000
     Health=1000
     ControllerClass=class'taskforces.SentinelController'
     bActorShadows=True
     bStasis=False
     mesh=SkeletalMesh'Cannons.FloorCannon'
     AmbientGlow=64
     bCollideWhenPlacing=True
     bDestroyInPainVolume=True
     bCanTeleport=False
     CollisionRadius=48.000000
     CollisionHeight=32.000000
     bUseCylinderCollision=True
     bBlockKarma=True
     bRotateToDesired=False
     Mass=1000.000000
     RotationRate=(Pitch=0,Yaw=0,Roll=0)
     Sname="Sentinel"
     cost=400
}
